package gqz.testdemo.DrawView;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2018/9/3.
 */

public class DrawView extends View {

    final String TAG = "DrawView-->";

    private Paint mPaint;
    private Path mPath;
    private float mLastX;
    private float mLastY;
    private Bitmap srcBmp;
    private Bitmap mBufferBitmap;
    private Canvas mBufferCanvas;

    private static final int MAX_CACHE_STEP = 20;

    private List<DrawingInfo> mDrawingList;
    private List<DrawingInfo> mRemovedList;

    private Xfermode mClearMode;
    private float mDrawSize;
    private float mEraserSize;

    private boolean mCanEraser;

    private Callback mCallback;

    public enum Mode {
        DRAW,
        ERASER
    }

    private Mode mMode = Mode.DRAW;

    public DrawView(Context context) {
        this(context, null);
    }

    public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setDrawingCacheEnabled(true);
        buildDrawingCache();
        init();
    }

    public interface Callback {
        void onUndoRedoStatusChanged();
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setFilterBitmap(true);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mDrawSize = 5;
        mEraserSize = 20;
        mPaint.setStrokeWidth(mDrawSize);
        mPaint.setColor(Color.BLACK);

        mClearMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
    }

    private static PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

    private void initBuffer() {
        mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        mBufferCanvas = new Canvas(mBufferBitmap);
        mBufferCanvas.setDrawFilter(pfd);
        mBufferCanvas.drawColor(Color.WHITE);
        srcW = getWidth();
        srcH = getHeight();
        srcBmp = Bitmap.createBitmap(mBufferBitmap);
    }

    private abstract static class DrawingInfo {
        Paint paint;

        abstract void draw(Canvas canvas);
    }

    private static class PathDrawingInfo extends DrawingInfo {
        Path path;

        @Override
        void draw(Canvas canvas) {
            canvas.drawPath(path, paint);
        }
    }

    public Mode getMode() {
        return mMode;
    }

    public void setMode(Mode mode) {
        if (mode != mMode) {
            mMode = mode;
            if (mMode == Mode.DRAW) {
                mPaint.setXfermode(null);
                mPaint.setStrokeWidth(mDrawSize);
            } else {
                mPaint.setXfermode(mClearMode);
                mPaint.setStrokeWidth(mEraserSize);
            }
        }
    }

    public void setEraserSize(float size) {
        mEraserSize = size;
    }

    public void setPenRawSize(float size) {
        mEraserSize = size;
    }

    public void setPenColor(int color) {
        mPaint.setColor(color);
    }

    public void setPenAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    private void reDraw() {
        if (mDrawingList != null) {
            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            for (DrawingInfo drawingInfo : mDrawingList) {
                drawingInfo.draw(mBufferCanvas);
            }
            invalidate();
        }
    }

    public boolean canRedo() {
        return mRemovedList != null && mRemovedList.size() > 0;
    }

    public boolean canUndo() {
        return mDrawingList != null && mDrawingList.size() > 0;
    }

    public void redo() {
        int size = mRemovedList == null ? 0 : mRemovedList.size();
        if (size > 0) {
            DrawingInfo info = mRemovedList.remove(size - 1);
            mDrawingList.add(info);
            mCanEraser = true;
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public void undo() {
        int size = mDrawingList == null ? 0 : mDrawingList.size();
        if (size > 0) {
            DrawingInfo info = mDrawingList.remove(size - 1);
            if (mRemovedList == null) {
                mRemovedList = new ArrayList<>(MAX_CACHE_STEP);
            }
            if (size == 1) {
                mCanEraser = false;
            }
            mRemovedList.add(info);
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public void clear() {
        if (mBufferBitmap != null) {
            if (mDrawingList != null) {
                mDrawingList.clear();
            }
            if (mRemovedList != null) {
                mRemovedList.clear();
            }
            mCanEraser = false;
            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            mBufferCanvas.drawColor(Color.WHITE);
            invalidate();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public Bitmap buildBitmap() {
        Bitmap bm = getDrawingCache();
        Bitmap result = Bitmap.createBitmap(bm);
        destroyDrawingCache();
        return result;
    }

    private void saveDrawingPath() {
        if (mDrawingList == null) {
            mDrawingList = new ArrayList<>(MAX_CACHE_STEP);
        } else if (mDrawingList.size() == MAX_CACHE_STEP) {
            mDrawingList.remove(0);
        }
        Path cachePath = new Path(mPath);
        Paint cachePaint = new Paint(mPaint);
        PathDrawingInfo info = new PathDrawingInfo();
        info.path = cachePath;
        info.paint = cachePaint;
        mDrawingList.add(info);
        mCanEraser = true;
        if (mCallback != null) {
            mCallback.onUndoRedoStatusChanged();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBufferBitmap != null) {
            canvas.drawBitmap(mBufferBitmap, 0, 0, null);
        }
    }

    float curX = 0, curY = 0;
    float firstX = 0, firstY = 0;
    public static final int MODE_DRAG = 1;//拖拽
    public static final int MODE_ZOOM = 2;//缩放
    int mode = 0, lastMode = 0;
    int srcW = 0, srcH = 0;
    float minScaleRatio = 1.0f;//最小放大比率
    float maxScaleRatio = 1.5f;//最大放大比率
    float curScallRatio = 1.0f;//当前缩放比率
    int distance = 0;
    int pointerCount = 0;

    public void setMode(int mode) {
        lastMode = this.mode;
        this.mode = mode;
    }

    private void getNewSizeView(Bitmap oldBmp, int w, int h, boolean isNeedScaleCanvas, float ratio) {
        ViewGroup.LayoutParams params = this.getLayoutParams();
        params.width = w;
        params.height = h;
        this.setLayoutParams(params);
        if (isNeedScaleCanvas) {
            Matrix matrix = new Matrix();
            matrix.postScale(ratio, ratio);
            mBufferBitmap = Bitmap.createBitmap(srcBmp, 0, 0, srcBmp.getWidth(), srcBmp.getHeight(), matrix, true);
            mBufferCanvas = new Canvas(mBufferBitmap);
//            mBufferCanvas.drawBitmap(mBufferBitmap, 0, 0, mPaint);
        } else {
            mBufferBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            mBufferCanvas = new Canvas(mBufferBitmap);
            mBufferCanvas.drawColor(Color.WHITE);
            mBufferCanvas.drawBitmap(oldBmp, 0, 0, mPaint);
            srcW = w;
            srcH = h;
        }
    }

    public void addSize(int w, int h) {
        Bitmap oldBmp = mBufferBitmap;
        Log.i(TAG, "addSize: old w*h: " + oldBmp.getWidth() + "*" + oldBmp.getHeight());
        int newW = oldBmp.getWidth() + w;
        int newH = oldBmp.getHeight() + h;
        getNewSizeView(oldBmp, newW, newH, false, 0);
    }

    private void scaleView(float ratio) {
        Log.i(TAG, "scaleView: ratio: " + ratio);
        curScallRatio = ratio;
        Bitmap oldBmp = mBufferBitmap;
        int newW = (int) (srcW * ratio);
        int newH = (int) (srcH * ratio);
        getNewSizeView(oldBmp, newW, newH, true, ratio);
    }

    private int getDistance(Point p1, Point p2) {
        int distance = 0;// 两点之间的距离
        int sideH = 0, sideV = 0;// 垂直边和水平边
        if (p2.x > p1.x) {
            sideV = p2.x - p1.x;
        } else {
            sideV = p1.x - p2.x;
        }
        if (p2.y > p1.y) {
            sideH = p2.y - p1.y;
        } else {
            sideH = p1.y - p2.y;
        }
        distance = (int) Math.sqrt((sideV * sideV) + (sideH * sideH));// 开根号
        return distance;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction() & MotionEvent.ACTION_MASK;
        float px1 = event.getX();
        float py1 = event.getY();


        switch (action) {
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "onTouchEvent: down");
                switch (mode) {
                    case 0:
                        mLastX = px1;
                        mLastY = py1;
                        if (mPath == null) {
                            mPath = new Path();
                        }
                        mPath.moveTo(px1, py1);
                        break;
                    case MODE_DRAG:
                        firstX = px1;
                        firstY = py1;
                        break;
                    case MODE_ZOOM:
                        break;
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
//                Log.i(TAG, "onTouchEvent: pointer down");
                lastMode = mode;
                mode = MODE_ZOOM;
                pointerCount = event.getPointerCount();
                if (pointerCount == 2) {
                    float x1 = event.getX();
                    float y1 = event.getY();
                    float x2 = event.getX(1);
                    float y2 = event.getY(1);
                    distance = getDistance(new Point((int) x1, (int) y1), new Point((int) x2, (int) y2));
                }
                break;
            case MotionEvent.ACTION_MOVE:
                switch (mode) {
                    case 0:
                        //这里终点设为两点的中心点的目的在于使绘制的曲线更平滑，如果终点直接设置为x,y，效果和lineto是一样的,实际是折线效果
                        mPath.quadTo(mLastX, mLastY, (px1 + mLastX) / 2, (py1 + mLastY) / 2);
                        if (mBufferBitmap == null) {
                            initBuffer();
                        }
                        if (mMode == Mode.ERASER && !mCanEraser) {
                            break;
                        }
                        mBufferCanvas.drawPath(mPath, mPaint);
                        invalidate();
                        mPath.reset();
                        mPath.moveTo((px1 + mLastX) / 2, (py1 + mLastY) / 2);
                        mLastX = px1;
                        mLastY = py1;
                        break;
                    case MODE_DRAG:
                        if (mBufferCanvas != null && mBufferBitmap != null) {
                            float x = px1 - firstX;
                            float y = py1 - firstY;
                            setX(curX += x);
                            setY(curY += y);
                        }
                        break;
                    case MODE_ZOOM:
//                        Log.i(TAG, "onTouchEvent: zoom");
                        if ((pointerCount = event.getPointerCount()) < 2) break;
                        float x1 = event.getX();
                        float y1 = event.getY();
                        float x2 = event.getX(1);
                        float y2 = event.getY(1);
                        int dis = getDistance(new Point((int) x1, (int) y1), new Point((int) x2, (int) y2));
                        int diffVal = dis - distance;
                        if (Math.abs(diffVal) > 15) {//阈值
                            float ratio = (float) diffVal / 10000f;//缩小每次缩放的比率，控制缩放速度
//                            Log.i(TAG, "scale ratio: " + ratio);
                            if (diffVal > 0) {//放大
                                if (curScallRatio < maxScaleRatio && curScallRatio + ratio <= maxScaleRatio) {
                                    scaleView(curScallRatio + ratio);
                                }
                            } else if (diffVal < 0) {//缩小
                                if (curScallRatio > minScaleRatio && curScallRatio + ratio >= minScaleRatio) {
                                    scaleView(curScallRatio + ratio);
                                }
                            }
                        }
                        break;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
//                Log.i(TAG, "onTouchEvent: pointer up");
                pointerCount = event.getPointerCount();
                break;
            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "onTouchEvent: up");
                switch (mode) {
                    case 0:
                        if (mMode == Mode.DRAW || mCanEraser) {
                            saveDrawingPath();
                        }
                        mPath.reset();
                        if (curScallRatio > 1) {
                            float ratio = 1 / curScallRatio;
                            Matrix matrix = new Matrix();
                            matrix.postScale(ratio, ratio);
                            srcBmp = Bitmap.createBitmap(mBufferBitmap, 0, 0, mBufferBitmap.getWidth(), mBufferBitmap.getHeight(), matrix, true);
                        } else srcBmp = Bitmap.createBitmap(mBufferBitmap);
                        break;
                    case MODE_DRAG:
                        break;
                    case MODE_ZOOM:
                        mode = lastMode;
                        break;
                }
                break;
        }
        return true;
    }

    public void drawBg() {
        mBufferCanvas.drawBitmap(srcBmp, 0, 0, mPaint);
        invalidate();
    }
}
